﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using LuaInterface;

namespace MND
{
    class Scenario
    {
        protected Queue<string> inbound = new Queue<string>();
        public void Enqueue(string s){
            if (!s.All(new Func<char,bool>(c => c.Equals('\n') || c.Equals('\r'))))
                inbound.Enqueue(s);
        }

        protected string[] script;
        protected Dictionary<string, int> labels = new Dictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);
        
        protected int currentLine = 0;
        protected bool pause = false;
        protected ResumeConditions? resumeCondition = null;
        protected string inputVarName;

        protected bool hasSetFunctions = false;

        protected Lua lua = new Lua();

        protected void Load(System.IO.FileInfo scriptFile)
        {
            if (scriptFile.Exists)
                Load(System.IO.File.ReadAllText(scriptFile.FullName));
            else throw new System.IO.FileNotFoundException();
        }

        protected void Load(string scriptText)
        {
            script = scriptText.Split(new char[]{'\n','\r'}, StringSplitOptions.RemoveEmptyEntries);
            for (var x = 0; x < script.Length; ++x)
            {
                string line = script[x];
                // Note labels
                if (line.Trim().StartsWith(":"))
                    labels.Add(line.Substring(1), x);
                script[x] = line.TrimStart();
            }
        }

        protected virtual void SetFunctions()
        {
            lua.RegisterFunction("goToStep", this, this.GetType().GetMethod("goToStepLabel"));
            lua.RegisterFunction("send", this, this.GetType().GetMethod("Send"));
        }

        public void SetLine(int lineNumber)
        {
            if (lineNumber > script.Length)
                throw new ArgumentOutOfRangeException();

            currentLine = lineNumber;
        }
        public void goToStepLabel(string label)
        {
            if (labels.ContainsKey(label))
                SetLine(labels[label]);
            else
                throw new ArgumentOutOfRangeException("No such label: "+label);
        }

        public virtual void Send(string s)
        {
            // Do nothing. Exists mostly as a stub, but does not warrant use of abstract.
        }

        public virtual void Tick()
        {
            if (resumeCondition != null)
            {
                switch (resumeCondition)
                {
                    case ResumeConditions.Tick:
                        pause = false;
                        resumeCondition = null;
                        break;
                    case ResumeConditions.UserInput:
                        lock (inbound)
                        {
                            if (inbound.Count > 0)
                            {
                                lua[inputVarName] = inbound.Dequeue();
                                pause = false;
                                resumeCondition = null;
                            }
                        }
                        break;
                }
            }
            Proceed();
        }

        protected void Proceed()
        {
            if (!hasSetFunctions)
                SetFunctions();

            if (!pause)
            {
                string line = String.Empty;
                char firstChar;

                StringBuilder luaScript = new StringBuilder();

                while (!pause)
                {
                    line = script[currentLine];
                    firstChar = line[0];
                    switch (firstChar)
                    {
                        case '>':
                            Send(line.Substring(1));
                            break;
                        case '<':
                            pause = true;
                            resumeCondition = ResumeConditions.UserInput;
                            inputVarName = line.Trim().Substring(1);
                            break;
                        case '#':
                            if (luaScript.Length > 0)
                            {
                                lua.DoString(luaScript.ToString());
                                luaScript.Clear();
                            }
                            break;
                        case '~':
                            pause = true;
                            break;
                        case ':':
                            // label, ignore
                            break;
                        default:
                            luaScript.AppendLine(line);
                            break;
                    }
                    ++currentLine;
                }
            }
        }

        internal enum ResumeConditions { UserInput, Tick }
    }
}
